/**
 * Created by Alex Bol on 2/19/2017.
 */

"use strict";

import Flatten from '../flatten';
import {Shape} from "./shape";
import {Matrix} from "./matrix";

/**
 * Class representing a vector
 * @type {Vector}
 */
export class Vector extends Shape {
    /**
     * Vector may be constructed by two points, or by two float numbers,
     * or by array of two numbers
     * @param {Point} ps - start point
     * @param {Point} pe - end point
     */
    constructor(...args) {
        super()
        /**
         * x-coordinate of a vector (float number)
         * @type {number}
         */
        this.x = 0;
        /**
         * y-coordinate of a vector (float number)
         * @type {number}
         */
        this.y = 0;

        /* return zero vector */
        if (args.length === 0) {
            return;
        }

        if (args.length === 1 && args[0] instanceof Array && args[0].length === 2) {
            let arr = args[0];
            if (typeof (arr[0]) == "number" && typeof (arr[1]) == "number") {
                this.x = arr[0];
                this.y = arr[1];
                return;
            }
        }

        if (args.length === 1 && args[0] instanceof Object && args[0].name === "vector") {
            let {x, y} = args[0];
            this.x = x;
            this.y = y;
            return;
        }

        if (args.length === 2) {
            let a1 = args[0];
            let a2 = args[1];

            if (typeof (a1) == "number" && typeof (a2) == "number") {
                this.x = a1;
                this.y = a2;
                return;
            }

            if (a1 instanceof Flatten.Point && a2 instanceof Flatten.Point) {
                this.x = a2.x - a1.x;
                this.y = a2.y - a1.y;
                return;
            }

        }

        throw Flatten.Errors.ILLEGAL_PARAMETERS;
    }

    /**
     * Method clone returns new instance of Vector
     * @returns {Vector}
     */
    clone() {
        return new Flatten.Vector(this.x, this.y);
    }


    /**
     实例1：计算两点之间的角度

     点a (x1, y1)
     点b (x2, y2)
     计算从a到b的角度

     1. 计算出两点之间的向量，
     2. 然后传入向量的y轴坐标和x轴坐标到atan2()方法中即可得到角度

     # Point A
     x1 = 0
     y1 = 0

     # Point B
     x2 = 1
     y2 = 1

     # Calculate magnitude and direction of vector AB
     dx = x2 - x1
     dy = y2 - y1

     result = math.atan2(dy, dx)

     */
    /**
     * 方向向量代表 直线的方向和倾斜程序。
     *
     * 矢量的斜率（弧度）
     * Slope of the vector in radians from 0 to 2PI
     * @returns {number}
     *
     *
     * atan: 计算反正切函数的方法
     * 返回值的单位是弧度制，需要将其转换为角度制后才能使用
     *
     *
     *
     *
     */
    get slope() {
        let angle = Math.atan2(this.y, this.x);
        if (angle < 0) angle = 2 * Math.PI + angle;
        return angle;
    }

    /**
     * Length of vector
     * @returns {number}
     */
    get length() {
        return Math.sqrt(this.dot(this));
    }

    /**
     * Returns true if vectors are equal up to [DP_TOL]{@link http://localhost:63342/flatten-js/docs/global.html#DP_TOL}
     * tolerance
     * @param {Vector} v
     * @returns {boolean}
     */
    equalTo(v) {
        return Flatten.Utils.EQ(this.x, v.x) && Flatten.Utils.EQ(this.y, v.y);
    }

    /**
     * Returns new vector multiplied by scalar
     * @param {number} scalar
     * @returns {Vector}
     */
    multiply(scalar) {
        return (new Flatten.Vector(scalar * this.x, scalar * this.y));
    }

    /**
     * https://baike.baidu.com/item/%E7%82%B9%E7%A7%AF/9648528
     * https://zhuanlan.zhihu.com/p/430854553?utm_id=0
     * https://blog.csdn.net/u011295947/article/details/71302864
     * 点积的作用：可以判断两个向量是否垂直。可以判断两个向量之间，是促进作用，还是抑制作用。可以做投影。
     * 返回两个向量的标量乘积（点积）
     * Returns scalar product (dot product) of two vectors <br/>
     * <code>dot_product = (this * v)</code>
     * @param {Vector} v Other vector
     * @returns {number}
     *
     * 几何表示：
     * this * v = |this| * |v| *cos夹角 = 是一个数值。
     * 几何意义：一个向量在另一个向量上的投影的长度，乘以另一个向量的模。
     *
     *  代数表示：
     *  this * v = （this.x*v.x + this.y * v.x）
     *  a = [x1,y1,z1, ...]
     *  b = [x2, y2, z2, ...]
     *  a * b = x1*x2 + y1*y2 + z1 * z2
     *
     */
    dot(v) {
        return (this.x * v.x + this.y * v.y);
    }

    /**返回两个向量的向量积（叉积）

     * Returns vector product (cross product) of two vectors <br/>
     * <code>cross_product = (this x v)</code>
     * @param {Vector} v Other vector
     * @returns {number}
     * 在二维平面中，两个向量的叉乘其结果（叉积）是一个确切的值
     * 二维向量叉乘：可以判断左右，判断是否共线。 为0 平行
     *
     * A * B = |A| |B| sin角度
     * 在几何上
     *   平行四边形的面积。
     * 代数
     *  this*v = this.x * v.y - this.y * v.x
     *
     */
    cross(v) {
        return (this.x * v.y - this.y * v.x);
    }

    /**返回单位矢量
     * Returns unit vector.<br/>
     * Throw error if given vector has zero length
     * @returns {Vector}
     */
    normalize() {
        if (!Flatten.Utils.EQ_0(this.length)) {
            return (new Flatten.Vector(this.x / this.length, this.y / this.length));
        }
        throw Flatten.Errors.ZERO_DIVISION;
    }

    /**返回按给定角度旋转的新矢量，
     * Returns new vector rotated by given angle,
     * 正角度定义了沿逆时针方向的旋转，
     * positive angle defines rotation in counterclockwise direction,
     * negative - in clockwise direction 负-顺时针方向
     * Vector only can be rotated around (0,0) point! 矢量只能绕（0,0）点旋转！
     * @param {number} angle - Angle in radians
     * @returns {Vector}
     */
    rotate(angle, center = new Flatten.Point()) {
        if (center.x === 0 && center.y === 0) {
            return this.transform(new Matrix().rotate(angle));
        }
        throw(Flatten.Errors.OPERATION_IS_NOT_SUPPORTED);
    }

    /**返回仿射变换矩阵m变换后的新矢量
     * Return new vector transformed by affine transformation matrix m
     * @param {Matrix} m - affine transformation matrix (a,b,c,d,tx,ty)
     * @returns {Vector}
     */
    transform(m) {
        return new Flatten.Vector(m.transform([this.x, this.y]))
    }

    /** 返回逆时针旋转90度的矢量
     * Returns vector rotated 90 degrees counterclockwise
     * @returns {Vector}
     */
    rotate90CCW() {
        return new Flatten.Vector(-this.y, this.x);
    };

    /** 返回顺时针旋转90度的矢量
     * Returns vector rotated 90 degrees clockwise
     * @returns {Vector}
     */
    rotate90CW() {
        return new Flatten.Vector(this.y, -this.x);
    };

    /**返回反向矢量
     * Return inverted vector
     * @returns {Vector}
     */
    invert() {
        return new Flatten.Vector(-this.x, -this.y);
    }

    /**返回将其他矢量添加到此矢量作为新矢量的结果
     * Return result of addition of other vector to this vector as a new vector
     * @param {Vector} v Other vector
     * @returns {Vector}
     *
     * 当前向量加上v向量
     */
    add(v) {
        return new Flatten.Vector(this.x + v.x, this.y + v.y);
    }

    /**返回当前矢量减去其他矢量的结果作为新矢量
     * Return result of subtraction of other vector from current vector as a new vector
     * @param {Vector} v Another vector
     * @returns {Vector}
     *
     * 当前向量减去v向量
     */
    subtract(v) {
        return new Flatten.Vector(this.x - v.x, this.y - v.y);
    }

    /**
     * Math.atan2()方法计算二维坐标系中任意一个点（x, y）和原点（0, 0）的连线与X轴正半轴的夹角大小。
     *
     * 此矢量与其他矢量之间的返回角度
     * Return angle between this vector and other vector. <br/>
     * 角度是在从当前矢量到另一个矢量的逆时针方向上从0到2*PI测量的。
     * Angle is measured from 0 to 2*PI in the counterclockwise direction from current vector to  another.
     * @param {Vector} v Another vector
     * @returns {number}
     * 笛卡尔坐标系下。
     * this向量与v量 逆时针方向的角度。
     *
     */
    angleTo(v) {
        let norm1 = this.normalize();
        let norm2 = v.normalize();
        let angle = Math.atan2(norm1.cross(norm2), norm1.dot(norm2));
        if (angle < 0) angle += 2 * Math.PI;
        return angle;
    }

    /**当前矢量在另一个矢量上的返回矢量投影
     * Return vector projection of the current vector on another vector
     * @param {Vector} v Another vector
     * @returns {Vector}
     * 即 this 向量在v向量上的投影向量
     */
    projectionOn(v) {
        let n = v.normalize();
        let d = this.dot(n);
        return n.multiply(d);
    }

    get name() {
        return "vector"
    }
}

Flatten.Vector = Vector;

/**
 * Function to create vector equivalent to "new" constructor
 * @param args
 */
export const vector = (...args) => new Flatten.Vector(...args);
Flatten.vector = vector;

